package info.u250.c2d.box2deditor.ui.util;

import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.LayoutManager2;
import java.awt.Point;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

/**
 *  The <code>OverlapLayout</code> class is a layout manager that
 *  lays out a container's components in an overlapping fashion. A
 *	component can be painted "above" or "below" the previous component
 *  in the container.
 *
 *  Like the GridLayout, each component is sized to the largest width and
 *  height of any component in the container. The amount of overlap is
 *  controlled by specifying the overlap postion of each component. You can
 *  simulate, left-to-right, right-to-left, top-to-bottom, bottom-to-top and
 *  diagonal layouts. As well you can "stack" components completely on top of
 *  one another. In this case the components are sized to the space available
 *  in the container.
 *
 *  A main usage for this layout might be in the creation of "card games". A
 *  few features have been added that might be handy in these cases:
 *
 *  a) a "popup" feature - when a component is selected in can "popup" from
 *     its regular location so it visibly stands out. To accomplish this some
 *     extra space must be reserved in the container for the popup. This is
 *     done by using the setPopupInsets method which allow you to control the
 *     popup direction. In addition you can add/remove a simple constraint to
 *     the component. POP_UP will popup the component. POP_DOWN or null
 *     will paint the component in its regular location.
 *  b) when a component is made "invisible" you can reserve its location in the
 *     container so all the other components don't shift.
 *
 *  Note: this layout is achieved by changing the ZOrder of components in the
 *  container. It will not work for all components as some compnents will
 *  always paint themselves on the top of others. This seems to happen with
 *  components like JButton as rollover effects are painted when a mouse moves
 *  over the components.
 */
public class OverlapLayout implements LayoutManager2, java.io.Serializable
{
	
	private static final long serialVersionUID = 1L;
	public static Boolean POP_UP = Boolean.TRUE;
	public static Boolean POP_DOWN = Boolean.FALSE;

	private static final int PREFERRED = 0;
	private static final int MINIMUM = 1;

	//  Indicates how a component is painted
	private boolean overlapAbove;

	private Point overlapPosition;

	//  Reserve space for invisible components in the Container
	private boolean includeInvisible = true;

	//  Reserve extra space so a component can "popup"
	private Insets popupInsets = new Insets(0, 0, 0, 0);

	//  Track original order in which the components where added
	private List<Component> components = new ArrayList<Component>();

	//  Track a constraint added to a component
	private HashMap<Component, Boolean> constraints = new HashMap<Component, Boolean>();

	//  Track maximum dimension of any component for easier layout
	private Dimension maximumSize = new Dimension();

	/**
	 *  Convenience constructor to provide for "stacking" of components. Each
	 *	component will be stacked above the previous component and sized to
	 *  fill the space of the parent container.
	 */
	public OverlapLayout()
	{
		this( new Point(0, 0) );
	}

	/**
	 *	Convenience constructor. Each component will overlap above the previous
	 *  component.
	 *
	 *  @param overlapPosition a Point defining the relative amount of overlap
	 */
	public OverlapLayout(Point overlapPosition)
	{
		this(overlapPosition, true);
	}

	/**
	 *  Create an overlapping layout.
	 *
	 *  @param overlapPosition a Point defining the relative amount of overlap
	 *  @param overlayAbove when true components are painted above the previous
	 *                      component, otherwise they are painted below.
	 */
	public OverlapLayout(Point overlapPosition, boolean overlapAbove)
	{
		setOverlapPosition( overlapPosition );
		this.overlapAbove = overlapAbove;
	}

	/**
	 *  When components are overlapped above the ZOrder of each component is
	 *  changed resulting in the components position in the container being
	 *  changed. For example when you add a component to the end of the
	 *  container it will be moved to the beginning. If you then try to access
	 *  the component using Component.componentAt(), you will get the first
	 *  component, not the last.
	 *
	 *  This method will convert the index to you access the proper component.
	 *
	 *  @param index the index to convert
	 */
	public int convertIndex(int index)
	{
		if (overlapAbove)
			return components.size() - index - 1;
		else
			return index;
	}

	/**
	 *  Get the include invisible property
	 *
	 *  @returns the include invisible property
	 */
	public boolean isIncludeInvisible()
	{
		return includeInvisible;
	}

	/**
	 *	Controls whether spaces should reserved for invisible components in
	 *  the container
	 *
	 *  @param includeInvisible  when true, space is reserved otherwise the
	 *                           component is not included in the layout sizing
	 */
	public void	setIncludeInvisible(boolean includeInvisible)
	{
		this.includeInvisible = includeInvisible;
	}

	/**
	 *  Get the overlapping position of each component
	 *
	 *  @returns the Point representing the overlapped position
	 */
	public Point getOverlapPosition()
	{
		return overlapPosition;
	}

	/**
	 *  Specify the position where the overlapped component should be painted.
	 *
	 *  @param overlapPosition the position where the next component is painted
	 */
	public void setOverlapPosition(Point overlapPosition)
	{
		this.overlapPosition = overlapPosition;
	}

	/**
	 *	Get the popup insets
	 *
	 *  @returns the popup insets
	 */
	public Insets getPopupInsets()
	{
		return popupInsets;
	}

	/**
	 *	Define extra space to be reserved by the container. This will allow
	 *  components to be "popped up" if required. Generally space would only be
	 *  reserved on one side of the container.
	 *
	 *  @param popupInsets Insets defining extra space for a particular side
	 *                     of the container.
	 */
	public void setPopupInsets(Insets popupInsets)
	{
		this.popupInsets = popupInsets;
	}

	/**
	 *  Gets the constraints for the specified component.
	 *
	 *  @param   component the component to be queried
	 *  @return  the constraint for the specified component, or null
	 *           if component is null or is not present in this layout
     */
    public Boolean getConstraints(Component component)
    {
    	return (Boolean)constraints.get(component);
    }

	/**
	 * Adds the specified component with the specified name to the layout.
	 * @param name the name of the component
	 * @param comp the component to be added
	 */
	public void addLayoutComponent(String name, Component comp) {}

	/*
	 *	Keep track of any specified constraint for the component.
	 */
	public void addLayoutComponent(Component component, Object constraint)
	{
		//  Support simple Boolean constraint for painting a Component in
		//  its "popped up" position

		if (constraint == null)
		{
			constraints.remove(component);
		}
		else if (constraint instanceof Boolean)
		{
			constraints.put(component, (Boolean)constraint);
		}
		else
		{
			String message = "Constraint parameter must be of type Boolean";
			throw new IllegalArgumentException( message );
		}

		//  Keep a separate List of components in the order in which they where
		//  added to the Container. This makes layout easier. First we need
		//  to find the position the component was added at. We can't depend
		//  on the component order in the parent Container as changing the
		//  Z-Order also changes the order in the Container

		if (!components.contains(component))
		{
			Container parent = component.getParent();
			int size = parent.getComponentCount();

			for (int i = 0 ; i < size ; i++)
			{
				Component c = parent.getComponent(i);

				if (c == component)
				{
					components.add(i, component);

					//  Need to change Z-Order so added components are painted
					//  above the previously added component.

					if (overlapAbove)
					{
						parent.setComponentZOrder(component, size - i - 1);
					}

		  	  		break;
				}
			}
		}
	}

	/**
	 * Removes the specified component from the layout.
	 *
	 * @param comp the component to be removed
	 */
	public void removeLayoutComponent(Component component)
	{
		components.remove( component );
		constraints.remove( component );
	}

	/**
	 *	Determine the minimum size on the Container
	 *
	 *  @param	 target   the container in which to do the layout
	 *  @return	 the minimum dimensions needed to lay out the
	 *			 subcomponents of the specified container
	 */
	public Dimension minimumLayoutSize(Container parent)
	{
		synchronized (parent.getTreeLock())
		{
			return getLayoutSize(parent, MINIMUM);
		}
	}

	/**
	 *	Determine the preferred size on the Container
	 *
	 *  @param	 parent   the container in which to do the layout
	 *  @return  the preferred dimensions to lay out the
	 *	         subcomponents of the specified container
	 */
	public Dimension preferredLayoutSize(Container parent)
	{
		synchronized (parent.getTreeLock())
		{
			return getLayoutSize(parent, PREFERRED);
		}
	}

	/*
	 *  The calculation for minimum/preferred size it the same. The only
	 *  difference is the need to use the minimum or preferred size of the
	 *  component in the calculation.
	 *
	 *  @param	 parent  the container in which to do the layout
	 *  @param	 type    either MINIMUM or PREFERRED
	 */
	private Dimension getLayoutSize(Container parent, int type)
	{
		int visibleComponents = 0;
		int width = 0;
		int height = 0;

		//  All components will be resized to the maximum dimension

		for (Component component: components)
		{
			if (component.isVisible()
			||  includeInvisible)
			{
				Dimension size = getDimension(component, type);
				width = Math.max(size.width, width);
				height = Math.max(size.height, height);
				visibleComponents++;
			}
		}

		if (visibleComponents == 0)
			return new Dimension(0, 0);

		//  Keep maximum dimension for easy access when laying out components

		if  (type == PREFERRED)
		{
			maximumSize.width = width;
			maximumSize.height = height;
		}

		//  Adjust size for each overlapping component

		visibleComponents--;
		width  += visibleComponents * Math.abs(overlapPosition.x);
		height += visibleComponents * Math.abs(overlapPosition.y);

		//  Adjust for parent Container and popup insets

		Insets parentInsets = parent.getInsets();
		width += parentInsets.left + parentInsets.right;
		height += parentInsets.top + parentInsets.bottom;

		width += popupInsets.left + popupInsets.right;
		height += popupInsets.top + popupInsets.bottom;

		return new Dimension(width, height);
	}

	private Dimension getDimension(Component component, int type)
	{
		switch (type)
		{
			case PREFERRED: return component.getPreferredSize();
			case MINIMUM: return component.getMinimumSize();
			default: return new Dimension(0, 0);
		}
	}

	/**
	 * Lays out the specified container using this layout.
	 * <p>
	 *
	 * @param	  target   the container in which to do the layout
	 */
	public void layoutContainer(Container parent)
	{
	synchronized (parent.getTreeLock())
	{
		int size = components.size();

		if (size == 0) return;

		//  Determine location of first component

		Point location = new Point(0, 0);
		Insets parentInsets = parent.getInsets();

		//  Layout right-to-left, else left-to-right

		if (overlapPosition.x < 0)
			location.x = parent.getWidth() - maximumSize.width - parentInsets.right - popupInsets.right;
		else
			location.x = parentInsets.left + popupInsets.left;

		//  Layout bottom-to-top, else top-to-bottom

		if (overlapPosition.y < 0)
			location.y = parent.getHeight() - maximumSize.height - parentInsets.bottom - popupInsets.bottom;
		else
			location.y = parentInsets.top + popupInsets.top;

		//  Set the size and location for each component

		for (int i = 0 ; i < size ; i++)
		{
			Component component = components.get(i);

			if (component.isVisible()
			||  includeInvisible)
			{
				//  When components are "stacked" resize each component to fill
				//  the size of the parent container

				if (overlapPosition.x == 0 && overlapPosition.y == 0)
				{
					int width = parent.getWidth() - parentInsets.left - parentInsets.right;
					int height = parent.getHeight() - parentInsets.top - parentInsets.bottom;
					component.setSize(width, height);
				}
				else  //  resize each component to be the same size
				{
					component.setSize( maximumSize );
				}

				//  Set location of the component

				int x = location.x;
				int y = location.y;

				//  Adjust location when component is "popped up"

				Boolean constraint = constraints.get(component);

				if (constraint != null
				&&  constraint == Boolean.TRUE)
				{
					x += popupInsets.right - popupInsets.left;
					y += popupInsets.bottom - popupInsets.top;
				}

				component.setLocation(x, y);

				//  Calculate location of next component using the overlap offsets

				location.x += overlapPosition.x;
				location.y += overlapPosition.y;
			}
		}
	}}

	/**
	 * There is no maximum.
	 */
	public Dimension maximumLayoutSize(Container target)
	{
		return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
	}

	/**
	 * Returns the alignment along the x axis.  Use center alignment.
	 */
	public float getLayoutAlignmentX(Container parent)
	{
		return 0.5f;
	}

	/**
	 * Returns the alignment along the y axis.  Use center alignment.
	 */
	public float getLayoutAlignmentY(Container parent)
	{
		return 0.5f;
	}

	/**
	 * Invalidates the layout, indicating that if the layout manager
	 * has cached information it should be discarded.
	 */
	public void invalidateLayout(Container target)
	{
		// remove constraints here?
	}

	/**
	 * Returns the string representation of this column layout's values.
	 * @return	 a string representation of this grid layout
	 */
	public String toString()
	{
		return getClass().getName()
			+ "[overlapAbove=" + overlapAbove
			+ ",overlapPosition=" + overlapPosition
			+ "]";
	}
}
